// ************************************************************* //
//                                                               //
//        Copyright (C) 2016 by Socionext Inc.                   //
//        All rights reserved.                                   //
//        Socionext Inc. Proprietary and Confidential.           //
//                                                               //
// ************************************************************* //
// #include "register.h"
#include "exstop.h"
#include "netsec.h"
// #include "timer.h"

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

/*****************************************************************************/
/**
 * @brief hash calculate
 * @param[in] data_num - number of data
 * @param[in] data[] - hash data
 * @param[out] hash_value[8] - hash value
 * @return D_NETSEC_OK
 * @return D_NETSEC_ERR_OUTOFRANGE
 * @return D_NETSEC_ERR_TIMEOUT
 * @return D_NETSEC_ERR_PARAM_NG
 * @note The calculation of the hash value
 */
/*****************************************************************************/
int hash_calculate(unsigned int data_num, T_HASH_DATA data[], unsigned long hash_value[8])
{
	unsigned int i,j;
	unsigned int last_num=0, data_cnt, data_cnt_last;
	int ret = D_NETSEC_ERR_PARAM_NG;
#if 1
	union {
		unsigned long word;
		unsigned char byte[4];
	} last_data = {0};
#else
	unsigned char last_data[4] = {0};
#endif
#if 1
	TickType_t system_time;
	TickType_t start_time;
#else
	T_SYSTIME system_time;
	unsigned long start_time;
#endif
	// paramter check
	for (i=0; i<data_num; i++) {
		if ((data[i].addr != 0) && (data[i].size != 0)) {
			ret = D_NETSEC_OK;
			break;
		}
	}
	if (ret != D_NETSEC_OK) {
		return D_NETSEC_ERR_PARAM_NG;
	}

	// check the data size of 4 byte units (except the last data)
	for (i=0; i<(data_num - 1); i++) {
		if ((data[i].size % 4) != 0) return D_NETSEC_ERR_OUTOFRANGE;
	}

	// Encryption Control Register
	IO_NETSEC.ENC_CTRL  = NETSEC_ENC_CTRL_SET;

	// Hash Algorithm Register
	IO_NETSEC.HASH_ALGO = NETSEC_HASH_256;

	// Hash Initialization Register
	IO_NETSEC.HASH_INIT = NETSEC_INIT_CODE;

	for (i=0; i<data_num; i++) {
		if ((data[i].addr == 0) || (data[i].size == 0)) {
			continue;
		}
		
		if (i < (data_num - 1)) {
			// except the last data
			for (j=0; j<data[i].size; j+=4) {
				// Encryption Hash Data Register
				IO_NETSEC.ENC_DATA = *((unsigned long*)data[i].addr);
				data[i].addr += 4;
			}
		}
		else {
			// last data
			data_cnt = (data[i].size / 4);
			last_num = (data[i].size % 4);

			if (last_num == 0) {
				data_cnt_last = 4;
				data_cnt--;
			}
			else {
				data_cnt_last = last_num;
			}

			for (j=0; j<data_cnt; j++) {
				// Encryption Hash Data Register
				IO_NETSEC.ENC_DATA = *((unsigned long*)data[i].addr);
				data[i].addr += 4;
			}

			for (j=0; j<data_cnt_last; j++) {
				last_data.byte[j] = *((unsigned char*)data[i].addr);
				data[i].addr++;
			}
		}
	}

	// Hash Command Register
	IO_NETSEC.HASH_CMD = (NETSEC_CMD_BASE | last_num);

	// Encryption Hash Data Register (last word)
	IO_NETSEC.ENC_DATA = last_data.word;

	// Hash Command Register (last word)
	IO_NETSEC.HASH_CMD = NETSEC_LAST_SHOT;

	// Status check start
#if 1
	start_time = xTaskGetTickCount();
#else
	get_tim_rom( &system_time ) ;
	start_time = system_time.ltime ;
#endif

	while(1) {
		// Encryption Status Register
		if ((IO_NETSEC.ENC_STATUS & NETSEC_HASH_DONE) != 0) break;

#if 1
		system_time = xTaskGetTickCount();
		if ((system_time - start_time) > NETSEC_WAIT_TIME) {
#else
		get_tim_rom( &system_time );
		if ((system_time.ltime - start_time) > NETSEC_WAIT_TIME) {
#endif
			// Timeout
			return D_NETSEC_ERR_TIMEOUT;
		}
	}

	// Hash Result Register
	for (i=0; i<8; i++) {
		hash_value[i] = IO_NETSEC.HASH_RESULT;
	}

	// Encryption Control Register
	IO_NETSEC.ENC_CTRL = NETSEC_ENC_CTRL_END;

	return D_NETSEC_OK;
}
